/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.extend.core;

import cn.star.framework.extend.schema.extract.internal.ColumnInformationEnhance;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.JDBCType;
import javax.persistence.Transient;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.TruthValue;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.tool.schema.extract.spi.ColumnInformation;
import org.hibernate.tool.schema.extract.spi.TableInformation;
import org.hibernate.type.Type;

/**
 * 列<br>
 *
 * @author zhaoweiping
 * @since 3.0.0
 */
@Slf4j
public class Column implements Serializable {
  /** 名称 */
  @Getter @Setter private String name;

  /** 注释 */
  @Getter @Setter private String comment;

  /** {@link ColumnInformation#getColumnSize()} */
  @Getter @Setter private int length = 255;

  /** {@link ColumnInformation#getDecimalDigits()} */
  @Getter @Setter private int precision = 0;

  /** {@link org.hibernate.mapping.Column#getScale()} */
  @Getter @Setter private int scale = 0;

  /** {@link ColumnInformation#getNullable()} */
  @Getter @Setter private boolean nullable = true;

  /** 是否唯一 */
  @Getter @Setter private boolean unique = false;

  /** 是否主键 */
  @Getter @Setter private boolean primary = false;

  /** 默认值 */
  @Getter @Setter private String defaultValue;

  /** {@link ColumnInformation#getTypeName()} */
  @Setter private String sqlType;

  /** {@link ColumnInformation#getTypeCode()} */
  @Setter private Integer sqlTypeCode;

  /** {@link JDBCType} */
  @Setter private JDBCType jdbcType;

  /**
   * {@link org.hibernate.mapping.Column#getSqlType(Dialect, Mapping)}<br>
   * 默认使用字符串类型
   */
  @Getter @Setter private Type basicType;

  /** 顺序 */
  @Getter @Setter private int order = 1;

  /** {@link org.hibernate.mapping.Column#getCheckConstraint()} */
  @Getter @Setter private String checkConstraint;

  /** {@link org.hibernate.mapping.Column#getCustomRead()} */
  @Getter @Setter private String customRead;

  /** {@link org.hibernate.mapping.Column#getCustomWrite()} */
  @Getter @Setter private String customWrite;

  /** 持久化{@link Transient} */
  @Getter @Setter private boolean persistence = true;

  /** 对应的Java字段 */
  @Getter @Setter private Field field;

  public Column() {}

  public Column(String name) {
    this.name = name;
  }

  public JDBCType getJdbcType() {
    if (jdbcType == null && sqlTypeCode != null) {
      JDBCType[] types = JDBCType.values();
      for (JDBCType type : types) {
        if (type.getVendorTypeNumber().equals(sqlTypeCode)) {
          return type;
        }
      }

      log.warn("{} mapping {} not found.", JDBCType.class.getName(), sqlTypeCode);
    }

    return jdbcType == null ? JDBCType.VARCHAR : jdbcType;
  }

  public Integer getSqlTypeCode() {
    if (sqlTypeCode == null) {
      return this.getJdbcType().getVendorTypeNumber();
    }

    return sqlTypeCode;
  }

  /**
   * 这个方法返回的类型没有经过字段长度和精度的计算，在使用的时候需要注意<br>
   * 推荐使用{@link #getSqlType(Dialect, Metadata)}<br>
   * 若使用该方法，则在设置的时候，就需要将长度和精度等信息直接设置到{@link #sqlType}中，而设置到{@link #length}、{@link #precision}、{@link
   * #scale}中的则不会生效
   *
   * @return 类型
   */
  public String getSqlType() {
    if (sqlType == null) {
      return this.getJdbcType().getName();
    }

    return sqlType;
  }

  public String getSqlType(Dialect dialect) {
    return dialect.getTypeName(this.getSqlTypeCode(), length, precision, scale);
  }

  public String getSqlType(Dialect dialect, Metadata metadata) {
    return basicType == null
        ? this.getSqlType(dialect)
        : dialect.getTypeName(basicType.sqlTypes(metadata)[0], length, precision, scale);
  }

  public static Column to(TableInformation tableInfo, ColumnInformation columnInfo) {
    Column column = new Column();

    column.setName(wrapper(columnInfo.getColumnIdentifier()));
    if (columnInfo instanceof ColumnInformationEnhance) {
      column.setComment(((ColumnInformationEnhance) columnInfo).getComment());
    }
    column.setLength(columnInfo.getColumnSize());
    column.setPrecision(columnInfo.getDecimalDigits());
    column.setNullable(columnInfo.getNullable() != TruthValue.FALSE);
    column.setSqlType(columnInfo.getTypeName());
    column.setSqlTypeCode(columnInfo.getTypeCode());
    column.setJdbcType(convert(columnInfo.getTypeCode()));

    return column;
  }

  private static String wrapper(Identifier identifier) {
    return identifier == null ? null : identifier.getText();
  }

  private static JDBCType convert(int sqlTypeCode) {
    JDBCType[] types = JDBCType.values();
    for (JDBCType type : types) {
      if (type.getVendorTypeNumber().equals(sqlTypeCode)) {
        return type;
      }
    }

    return null;
  }
}
